import {Cell, Edge, Graph} from "@antv/x6";

import './definition.css'
import {Snapline} from "@antv/x6-plugin-snapline";
import {Transform} from "@antv/x6-plugin-transform";
import {Component} from "react";
import {message} from "antd";

// 公司
export const SHAPE_COM = 'cm'

// 拟新设公司
export const SHAPE_NEW_COM = 'ncm'

// 拟注销公司
export const SHAPE_CANCELED_COM = 'ccm'

// 自然人
export const SHAPE_HOLDER = 'hol'

// 边
export const SHAPE_EDGE = 'edge'

// 文本框
export const SHAPE_TXT = 'txt'

const PORTS_DEF = [{group: 'top'}, {group: 'right'}, {group: 'bottom'}, {group: 'left'}]

const CIRCLE_DEF = {
    r: 4,
    magnet: true,
    strokeWidth: 1,
    visibility: 'hidden'
}

export const PORTS = {
    groups: {
        top: {position: 'top', allowMultiple: true, attrs: {circle: CIRCLE_DEF}},
        right: {position: 'right', allowMultiple: true, attrs: {circle: CIRCLE_DEF}},
        bottom: {position: 'bottom', allowMultiple: true, attrs: {circle: CIRCLE_DEF}},
        left: {position: 'left', allowMultiple: true, attrs: {circle: CIRCLE_DEF}}
    },
    items: PORTS_DEF
};

// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

/**
 * Description: "普通公司" 的节点定义.
 * @author <a href="mailto:scorpio.caplike@gmail.com">LiKe</a> @ 2025-05-03 10:37:43
 */
Graph.registerNode(SHAPE_COM, {
        inherit: 'rect', width: 90, height: 50,
        attrs: {
            body: {strokeWidth: 1}
        },
        tools: ['node-editor'], ports: PORTS
    },
    true
)

/**
 * Description: "拟新设公司" 的节点定义.
 * @author <a href="mailto:scorpio.caplike@gmail.com">LiKe</a> @ 2025-05-03 10:38:49
 */
Graph.registerNode(SHAPE_NEW_COM, {
        inherit: 'rect', width: 90, height: 50,
        attrs: {
            body: {strokeWidth: 1}
        },
        tools: ['node-editor'], ports: PORTS
    },
    true
)

/**
 * Description: "拟注销公司" 的节点定义.
 * @author <a href="mailto:scorpio.caplike@gmail.com">LiKe</a> @ 2025-05-03 11:15:50
 */
Graph.registerNode(SHAPE_CANCELED_COM, {
        inherit: 'rect', width: 90, height: 50,
        attrs: {
            body: {strokeWidth: 1}
        },
        tools: ['node-editor'], ports: PORTS
    },
    true
)

/**
 * Description: "自然人" 的节点定义.
 * @author <a href="mailto:scorpio.caplike@gmail.com">LiKe</a> @ 2025-05-03 10:39:37
 */
Graph.registerNode(SHAPE_HOLDER, {
        inherit: 'circle', width: 65, height: 65,
        attrs: {
            body: {strokeWidth: 1}
        },
        tools: ['node-editor'], ports: PORTS
    },
    true
)

/**
 * Description: "边" 的节点定义.
 * @author <a href="mailto:scorpio.caplike@gmail.com">LiKe</a> @ 2025-05-03 20:36:05
 */
Graph.registerEdge(SHAPE_EDGE, {
        inherit: 'edge',
        attrs: {
            line: {strokeWidth: 1}
        },
        tools: ['vertices']
    },
    true
)

/**
 * Description: "文本框" 的节点定义.
 * @author <a href="mailto:scorpio.caplike@gmail.com">LiKe</a> @ 2025-05-03 10:39:37
 */
Graph.registerNode(SHAPE_TXT, {
        inherit: 'rect', width: 120, height: 80,
        attrs: {
            body: {strokeWidth: 1, strokeDasharray: '5 5', rx: 5, ry: 5},
        },
        tools: ['node-editor']
    },
    true
)

// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

export function buildGraph(container: HTMLElement | undefined) {
    const graph = new Graph({
        container: container,  // 画布的容器
        autoResize: true,      // 是否监听容器大小改变，并自动更新画布大小
        // panning: true,
        mousewheel: true,
        background: {color: '#F2F7FA'},

        connecting: {
            // @formatter:off
            allowBlank: false,  // 是否允许连接到画布空白位置的点，默认为 true
            allowLoop:  false,  // 是否允许创建循环连线，即边的起始节点和终止节点为同一节点，默认为 true
            allowNode:  false,  // 是否允许边连接到节点（非节点上的连接桩），默认为 true
            allowEdge:  false,  // 是否允许边连接到另一个边，默认为 true
            allowPort:  true,   // 是否允许边连接到连接桩，默认为 true
            allowMulti: false,  // 是否允许在相同的起始节点和终止之间创建多条边，默认为 true
            // @formatter:on

            createEdge(): Edge {
                return graph.createEdge({
                    shape: 'edge'
                })
            },
            validateConnection({sourceCell, targetCell}) {
                if (sourceCell && targetCell) {
                    return sourceCell.id !== targetCell.id; // 不允许连接自己
                }
                return false;
            },
        },
        grid: {
            visible: true, type: 'doubleMesh',
            args: [
                {
                    color: '#eee', // 主网格线颜色
                    thickness: 1, // 主网格线宽度
                },
                {
                    color: '#ddd', // 次网格线颜色
                    thickness: 1, // 次网格线宽度
                    factor: 4, // 主次网格线间隔
                },
            ],
        },
    });
    return graph
}

/**
 * Description: 图形工具.
 * @author <a href="mailto:scorpio.caplike@gmail.com">LiKe</a> @ 2025-05-03 10:54:49
 */
export class GraphTools {
    private graph: Graph;

    /**
     * Description: 显示/隐藏所有节点的连接桩.
     * @author <a href="mailto:scorpio.caplike@gmail.com">LiKe</a> @ 2025-05-03 17:51:34
     */
    private toggleNodePorts(state: string) {
        this.graph?.getNodes().forEach(node => {
            node.getPorts().forEach(port => {
                node.portProp(port.id!, 'attrs/circle/visibility', state)
            })
        })
    }

    /**
     * Description: 验证节点的持股比例.
     * @author <a href="mailto:scorpio.caplike@gmail.com">LiKe</a> @ 2025-05-03 18:50:46
     */
    private validateCellProportion(cell: Cell, proportion: number | null) {
        const connectedEdges = this.graph?.getConnectedEdges(cell);
        const proportions: number[] = []
        if (proportion) {
            proportions.push(proportion)
        }
        connectedEdges?.forEach((edge) => {
            let labels = edge.getLabels();
            if (labels.length == 0) {
                return
            }

            let labelText = labels[0].attrs?.label.text;
            if (!labelText) {
                return
            }
            let labelString = labelText as String
            if (labelString.endsWith('%')) {
                proportions.push(Number(labelString.substring(0, labelString.length - 1)))
            }
        })

        const overallProportion = proportions.reduce((prev, curr) => prev + curr, 0);
        if (overallProportion > 100) {
            throw Error(`持股比例不能超过 100% (当前: ${overallProportion}%)`)
        }
    }

    constructor(graph: Graph) {
        this.graph = graph;

        this.graph
            .use(
                new Snapline({enabled: true})
            )
            .use(
                new Transform({
                    resizing: {
                        enabled: true,
                        minWidth: 1, maxWidth: 400, minHeight: 1, maxHeight: 200,
                        orthogonal: false, restrict: false,
                        preserveAspectRatio: true, // 保持宽高比
                    }
                })
            )
        console.log('[GraphTools] Graph tools loaded.');
    }

    // ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    /**
     * Description: 注册画布事件.
     *
     * @param graphModal (Component) 画布的 Modal 对象
     * @return GraphTools 工具本身
     * @author <a href="mailto:scorpio.caplike@gmail.com">LiKe</a> @ 2025-05-03 17:42:52
     */
    registerEvents(graphModal: Component): GraphTools {
        this.graph
            // ~ "节点" 事件
            .on('node:mouseover', () => this.toggleNodePorts('visible'))
            .on('node:mouseleave', () => this.toggleNodePorts('hidden'))
            .on('node:contextmenu', ({node}) => node.remove()) // 右键删除

            // ~ "边" 事件
            .on('edge:connected', ({edge}) => {
                const targetNode = this.graph?.getCellById(edge.getTargetCellId());
                if (!targetNode) return

                const targetShape = targetNode.shape
                // ~ Rule: 如果连线的目标节点是 "拟新设公司" 或者 "拟注销公司" - 使用虚线
                if ([SHAPE_NEW_COM, SHAPE_CANCELED_COM].includes(targetShape)) {
                    edge.setAttrs({line: {strokeDasharray: '5 5'}})
                }

                if (SHAPE_CANCELED_COM == targetShape) {
                    edge.setLabels('X')  // ~ Rule: "拟注销公司" 的线中间打个叉
                } else {
                    graphModal.setState({proportionalEdge: edge, proportionalModalVisible: true})  // ~ Rule: 提示输入股权比例
                }
            })
            .on('edge:contextmenu', ({edge}) => edge.remove()) // 右键删除

        console.log('[GraphTools] Graph events register successfully.');
        return this
    }

    /**
     * Description: 为边打上标签 (股权占比).
     * @author <a href="mailto:scorpio.caplike@gmail.com">LiKe</a> @ 2025-05-03 16:44:35
     */
    setEdgeLabel(graphModel: Component<any, any>) {
        const {proportionalEdge, proportionalModalValue} = graphModel.state
        if (!proportionalEdge) {
            return
        }

        // ~ 执行验证
        try {
            this.validateCellProportion(this.graph?.getCellById((proportionalEdge as Edge).getTargetCellId()), proportionalModalValue);
        } catch (error) {
            if (error instanceof Error) {
                message.error(error.message).then(() => {
                })
                return
            }
        }

        (proportionalEdge as Edge).setLabels(proportionalModalValue + '%')

        // ~ 恢复现场
        graphModel.setState({
            proportionalEdge: null,
            proportionalModalVisible: false,
            proportionalModalValue: 100
        })
    }

    // ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    center() {
        this.graph?.centerContent()
    }

    /**
     * Description: 手动添加 "公司".
     * @author <a href="mailto:scorpio.caplike@gmail.com">LiKe</a> @ 2025-03-23 16:38:24
     */
    addCompany() {
        this.graph?.addNode({shape: SHAPE_COM, label: '公司'})
    }

    /**
     * Description: 手动添加 "拟新设公司".
     * @author <a href="mailto:scorpio.caplike@gmail.com">LiKe</a> @ 2025-05-03 10:52:41
     */
    addNewCompany() {
        this.graph?.addNode({shape: SHAPE_NEW_COM, label: '拟新设公司'})
    }

    /**
     * Description: 手动添加 "拟注销公司".
     * @author <a href="mailto:scorpio.caplike@gmail.com">LiKe</a> @ 2025-05-03 10:53:46
     */
    addCanceledCompany() {
        this.graph?.addNode({shape: SHAPE_CANCELED_COM, label: '拟注销公司'})
    }

    /**
     * Description: 手动添加 "自然人".
     * @author <a href="mailto:scorpio.caplike@gmail.com">LiKe</a> @ 2025-03-23 16:38:45
     */
    addHolder() {
        this.graph?.addNode({shape: SHAPE_HOLDER, label: '自然人'})
    }

    /**
     * Description: 手动添加 "文本框".
     * @author <a href="mailto:scorpio.caplike@gmail.com">LiKe</a> @ 2025-05-03 20:32:51
     */
    addText() {
        this.graph?.addNode({shape: SHAPE_TXT, label: '文本框'})
    }
}
